You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
157 lines
4.9 KiB
157 lines
4.9 KiB
import Link from "next/link";
|
|
import {
|
|
PopiartApiError,
|
|
getSkillsCatalog,
|
|
getViewerSession,
|
|
} from "@/lib/popiart-api";
|
|
import { getLiveCopy } from "@/lib/live-copy";
|
|
import { type Locale } from "@/lib/site-content";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
function formatError(error: unknown) {
|
|
if (error instanceof PopiartApiError) {
|
|
return error.message;
|
|
}
|
|
if (error instanceof Error) {
|
|
return error.message;
|
|
}
|
|
return String(error);
|
|
}
|
|
|
|
export default async function SkillsPage({
|
|
params,
|
|
searchParams,
|
|
}: {
|
|
params: Promise<{ locale: string }>;
|
|
searchParams: Promise<{ search?: string | string[] }>;
|
|
}) {
|
|
const { locale } = await params;
|
|
const rawSearch = (await searchParams).search;
|
|
const search = Array.isArray(rawSearch) ? rawSearch[0] : rawSearch;
|
|
const typedLocale = locale as Locale;
|
|
const liveCopy = getLiveCopy(typedLocale);
|
|
const session = await getViewerSession();
|
|
|
|
if (!session) {
|
|
return (
|
|
<div className="page-stack">
|
|
<section className="section-panel hero-tight">
|
|
<div className="section-heading">
|
|
<div className="eyebrow">{liveCopy.skills.tag}</div>
|
|
<h1>{liveCopy.skills.title}</h1>
|
|
<p>{liveCopy.skills.subtitle}</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="section-panel empty-state-card">
|
|
<div className="section-heading compact">
|
|
<h2>{liveCopy.skills.unauthenticatedTitle}</h2>
|
|
<p>{liveCopy.skills.unauthenticatedBody}</p>
|
|
</div>
|
|
<div className="hero-actions">
|
|
<Link className="button button-dark" href={`/${locale}/login`}>
|
|
{liveCopy.skills.loginCta}
|
|
</Link>
|
|
<Link className="button button-light" href={`/${locale}/docs`}>
|
|
{liveCopy.skills.docsCta}
|
|
</Link>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
let catalog = null;
|
|
let loadError: string | null = null;
|
|
|
|
try {
|
|
catalog = await getSkillsCatalog(search?.trim());
|
|
} catch (error) {
|
|
loadError = formatError(error);
|
|
}
|
|
|
|
return (
|
|
<div className="page-stack">
|
|
<section className="section-panel hero-tight">
|
|
<div className="section-heading">
|
|
<div className="eyebrow">{liveCopy.skills.tag}</div>
|
|
<h1>{liveCopy.skills.title}</h1>
|
|
<p>{liveCopy.skills.subtitle}</p>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="section-panel">
|
|
<form className="search-form" method="get">
|
|
<label className="field-label" htmlFor="skill-search">
|
|
{liveCopy.skills.searchLabel}
|
|
</label>
|
|
<div className="search-row">
|
|
<input
|
|
className="text-input"
|
|
defaultValue={search ?? ""}
|
|
id="skill-search"
|
|
name="search"
|
|
placeholder={liveCopy.skills.searchPlaceholder}
|
|
type="search"
|
|
/>
|
|
<button className="button button-dark" type="submit">
|
|
{liveCopy.skills.searchAction}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
{loadError ? (
|
|
<div className="status-banner status-banner-error">
|
|
<strong>{liveCopy.skills.loadErrorPrefix}</strong>
|
|
<span>{loadError}</span>
|
|
</div>
|
|
) : null}
|
|
|
|
<div className="pill-row">
|
|
<span className="pill">
|
|
{liveCopy.skills.resultsPrefix}: {catalog?.total ?? 0}
|
|
</span>
|
|
<span className="pill">{session.user.name || session.user.email || session.user.id}</span>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="catalog-grid">
|
|
{catalog && catalog.items.length > 0 ? (
|
|
catalog.items.map((skill) => (
|
|
<article className="feature-card skill-card" key={skill.id}>
|
|
<span className="card-kicker">{skill.version}</span>
|
|
<h3>{skill.name}</h3>
|
|
<p>{skill.description}</p>
|
|
<dl className="meta-grid">
|
|
<div>
|
|
<dt>{liveCopy.skills.routeKey}</dt>
|
|
<dd>{skill.route_key || skill.id}</dd>
|
|
</div>
|
|
<div>
|
|
<dt>{liveCopy.skills.modelType}</dt>
|
|
<dd>{skill.model_type}</dd>
|
|
</div>
|
|
<div>
|
|
<dt>{liveCopy.skills.latency}</dt>
|
|
<dd>{skill.estimated_duration_s}s</dd>
|
|
</div>
|
|
<div>
|
|
<dt>{liveCopy.skills.tags}</dt>
|
|
<dd>{skill.tags.join(", ") || "-"}</dd>
|
|
</div>
|
|
</dl>
|
|
</article>
|
|
))
|
|
) : (
|
|
<article className="section-panel empty-state-card">
|
|
<div className="section-heading compact">
|
|
<h2>{liveCopy.skills.noResults}</h2>
|
|
<p>{search ? `"${search}"` : liveCopy.skills.subtitle}</p>
|
|
</div>
|
|
</article>
|
|
)}
|
|
</section>
|
|
</div>
|
|
);
|
|
}
|
|
|